42장. 도커 기초 — 이미지 · 컨테이너 · 레이어
이 장에서 말하고자 하는 것
앞 장에서 우리는 컨테이너가 무엇이고 왜 쓰는지를 봤다.
이제 그 컨테이너를 실제로 만드는 도구 Docker 의 핵심 세 개념을 잡는다.
- 이미지(Image)
- 컨테이너(Container)
- 레이어(Layer)
1. 이미지와 컨테이너 — 다른 것이다
이미지 : 실행되지 않은 설계도 (붕어빵 틀)
컨테이너 : 그 설계도로 실행된 인스턴스 (붕어빵)
my-app:v1 ← 이미지 1개
↓ docker run
컨테이너 1, 2, 3 ... ← 같은 이미지로 컨테이너 여러 개
이미지는 변하지 않는다. 컨테이너는 살아 있는 프로세스다.
2. Dockerfile — 이미지의 설계도
FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 8080
CMD ["node", "server.js"]
각 줄이 이미지에 한 단계씩 쌓아 올라간다.
3. 레이어 — 이미지의 속살
이미지는 단일 파일이 아니라 여러 레이어 의 누적이다.
[Layer A: 베이스 OS]
[Layer B: 의존성]
[Layer C: 코드]
Dockerfile의 각 명령이 한 레이어를 만든다.
이 구조 덕분에
같은 베이스 + 같은 의존성이면 캐시를 재사용한다
코드만 바꾸면 그 레이어만 다시 만들어진다.
4. 빌드 캐시를 살리는 순서
# 좋은 순서
COPY package.json package-lock.json ./
RUN npm ci
COPY . . ← 코드 변경 시에도 npm ci 캐시 살아남
# 나쁜 순서
COPY . .
RUN npm ci ← 코드가 조금만 바뀌어도 의존성 다시 설치
자주 안 바뀌는 명령을 위에, 자주 바뀌는 명령을 아래에
5. Multi-stage Build — 가벼운 이미지
# 빌드 단계
FROM node:20-alpine AS build
WORKDIR /app
COPY . .
RUN npm ci && npm run build
# 실행 단계
FROM node:20-alpine
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]
빌드 도구는 빌드 단계에만, 최종 이미지에는 실행에 필요한 것만.
이미지가 작아지면 push · pull · 배포가 다 빨라진다
6. 우리 서비스에서 컨테이너의 표준 모양
- alpine 또는 distroless 베이스
- multi-stage build
- 빌드 캐시를 살리는 COPY 순서
- CMD 하나 (한 프로세스)
- 비루트 사용자
/health엔드포인트 또는 HEALTHCHECK
7. 직접 확인해보기 — CLI
docker build -t orders:v1 .
docker run -p 8080:8080 orders:v1
docker ps
docker logs <container-id>
docker exec -it <container-id> sh
docker history orders:v1
docker history 는 이미지가 어떻게 만들어졌는지 한눈에 본다.
8. 코드로는 이렇게 생겼다 — 운영용 Dockerfile
FROM node:20-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
RUN addgroup -S app && adduser -S app -G app
USER app
COPY --from=build --chown=app:app /app/dist ./dist
COPY --from=build --chown=app:app /app/node_modules ./node_modules
COPY --from=build --chown=app:app /app/package.json ./
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget -qO- http://localhost:8080/health || exit 1
CMD ["node", "dist/server.js"]
9. 이렇게 쓰면 망한다 — 안티패턴
안티패턴 1. 모든 걸 한 줄에 우겨넣는다
RUN apt-get update && apt-get install -y nodejs && git clone ... && npm ci && npm run build
레이어 캐시를 활용 못 한다.
안티패턴 2. 루트로 실행한다
기본 도커는 root로 실행. 보안 사고가 호스트까지 영향 줄 수 있다.
USER app으로 비루트 전환
안티패턴 3. 비밀 키 · 토큰을 이미지에 박는다
ENV DB_PASSWORD=mypassword123
이미지에 남으면 어디서든 읽을 수 있다.
비밀은 Secrets Manager · Parameter Store · 환경 변수로 주입
안티패턴 4. OS 도구를 가득 깔아둔다
vim, curl, ssh 가 다 들어가면 공격 표면이 넓어진다.
실행에 필요한 것만 남기고 distroless 고려
10. 한 줄로 정리
Dockerfile은 이미지의 설계도이며,
레이어 캐시와 multi-stage 빌드를 살리는 게 곧 운영 품질이다
11. 이 장의 핵심 정리
- 이미지는 설계도, 컨테이너는 실행 인스턴스다.
- 이미지는 레이어의 누적이며, COPY/RUN 순서가 캐시를 결정한다.
- Multi-stage build로 빌드 도구를 최종 이미지에서 분리한다.
- 비루트 사용자, 작은 베이스, 비밀 제거가 보안의 기본이다.
/health엔드포인트와 HEALTHCHECK는 모든 운영 이미지에 있어야 한다.